package com.heo.server;

import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;
import org.springframework.stereotype.Component;

import com.heo.protocol.DeviceProtocolDecoder;
import com.heo.protocol.DeviceProtocolEncoder;
import com.heo.pub.ApplicationContextUtil;
import com.heo.pub.ServerConfig;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.timeout.IdleStateHandler;

/**
 * Created by zhangcf
 * 服务端程序主入口类
 */
@Component
public class SocketServerBootstrap
{
  private static Logger log = Logger.getLogger(SocketServerBootstrap.class);
  private int port = ServerConfig.PORT;

  private static final void shutdownCallback()
  {
    System.out.println("Shutdown callback is invoked.");
    log.info("Socket Server is shutdown.");
  }

  public SocketServerBootstrap() throws InterruptedException
  {
//    bind();
  }

  public void bind() throws InterruptedException
  {
    EventLoopGroup boss = new NioEventLoopGroup();// 创建处理连接的线程池:bossGroup，boss线程负责监听新的连接请求，当有新的连接进来时，将对应的channel指派一个worker线程来处理。
    EventLoopGroup worker = new NioEventLoopGroup();// 创建处理所有事件的线程池:workerGroup，Worker线程负责对该Channel的读写操作。
    try
    {
      // 指定需要执行的childHandler
      // 设置部分参数，如AdaptiveRecvByteBufAllocator缓存大小
      ServerBootstrap bootstrap = new ServerBootstrap();
      bootstrap.group(boss, worker);// 传入上面开辟的线程池
      bootstrap.channel(NioServerSocketChannel.class);// 指定连接该服务器的channel类型
      bootstrap.option(ChannelOption.SO_BACKLOG, 1024);// 当服务器请求处理线程全满时，用于临时存放已完成三次握手的请求的队列的最大长度。如果未设置或所设置的值小于1，Java将使用默认值50。
//      bootstrap.option(ChannelOption.SO_TIMEOUT, ServerConfig.CONNECT_TIME);//socket超时
      bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, ServerConfig.CONNECT_TIME_OUT);// 客户端连接超时，毫秒，netty超时
      bootstrap.childOption(ChannelOption.TCP_NODELAY, true);// 如果要求高实时性，有数据发送时就马上发送，就将该选项设置为true
      bootstrap.childOption(ChannelOption.SO_KEEPALIVE, true);// 是否启用心跳保活机制。在双方TCP套接字建立连接后（即都进入ESTABLISHED状态）并且在两个小时左右上层没有任何数据传输的情况下，这套机制才会被激活。
      bootstrap.childHandler(new ChannelInitializer<SocketChannel>()// 构建Handler处理流程
      {
        @Override
        protected void initChannel(SocketChannel socketChannel) throws Exception
        {
          ChannelPipeline p = socketChannel.pipeline();
          // FixedLengthFrameDecoder:是固定长度解码器，它能按照指定的长度对消息进行自动解码，开发者不需要考虑TCP的粘包等问题。
          // 利用FixedLengthFrameDecoder解码，无论一次性接收到多少的数据，他都会按照构造函数中设置的长度进行解码；
          // 如果是半包消息，FixedLengthFrameDecoder会缓存半包消息并等待下一个包，到达后进行拼包，直到读取完整的包。
          // 包长解码器：定义一个LengthFieldBasedFrameDecoder(1024*1024*1024,
          // 0,4,0,4))，最大数据量是1GB,长度属性位于数据包头部，占4个字节，协议体调节值为0，跳过头部协议长度四个字节
//          p.addLast(new FixedLengthFrameDecoder(10));// 定长数据包解码器，每10位一个包
          p.addLast(new DeviceProtocolDecoder());
          p.addLast(new DeviceProtocolEncoder());
          p.addLast(new IdleStateHandler(ServerConfig.READ_IDLE_TIME, ServerConfig.WRITE_IDLE_TIME, ServerConfig.ALL_IDLE_TIME));// 心跳检测线程，3个参数分别代表，读超时，写超时，读写超时，单位是秒
//          p.addLast(new NettyServerHandler());// 不用spring的情况下，定义handler的方式
          p.addLast(ApplicationContextUtil.getApplicationContext().getBean(SocketServerHandler.class));//用spring的情况下，获取handler的方式。在对应的handler道理定义该bean为多例
        }
      });
      // 阻塞等待服务器完全启动,线程同步阻塞等待服务器绑定到指定端口
      ChannelFuture f = bootstrap.bind(port).sync();
      if (f.isSuccess())
      {
        log.info("SocketServer服务器启动成功。监听端口[" + port + "]");
      }
      // 成功绑定到端口之后,给channel增加一个管道关闭的监听器并同步阻塞,直到channel关闭,线程才会往下执行,结束进程。等待服务器退出
      f.channel().closeFuture().sync();
    }
    catch (Exception e)
    {
      log.error(e.getMessage(), e);
    }
    finally
    {
      // 优雅退出，释放线程池资源
      boss.shutdownGracefully();
      worker.shutdownGracefully();
      log.info("SocketServer服务器关闭。");
    }
  }

  public static void main(String[] args) throws InterruptedException
  {
    Runtime.getRuntime().addShutdownHook(new Thread()
    {
      @Override
      public void run()
      {
        shutdownCallback();
      }
    });
    
    PropertyConfigurator.configure(SocketServerBootstrap.class.getResource("/").getPath() + "/com/heo/config/log4j.properties");
    SocketServerBootstrap bootstrap = new SocketServerBootstrap();
    bootstrap.bind();

  }
}
